Description

Use historical draw results, and number of hunters to train a model we can use to predict the number of hunters in future years.

TODO - Include other potential inputs that could impact how many hunters get a license and show up. Those could include economic indicators, and costs associated with hunting (transportation, lodging).

NOTICE that I am only looking at the general rifle hunting seasons on public land. There are also hunters for Archery, Muzzleloader, Private Land, Ranching for Wildlife, etc.


Setup

Load required libraries for wrangling data, charting, and mapping

library(plyr,quietly = T) # data wrangling
library(dplyr,quietly = T) # data wrangling
library(ggplot2, quietly = T) # charting
library(ggthemes,quietly = T) # so I can add the highcharts theme and palette
library(scales,quietly = T) # to load the percent function when labeling plots
library(caret,quietly = T) # classification and regression training
library(foreach,quietly = T) # parallel processing to speed up the model training
library(doMC,quietly = T) # parallel processing to speed up the model training
library(lubridate,quietly = T) # for timing models

Set our preferred charting theme

theme_set(theme_minimal()+theme_hc()+theme(legend.key.width = unit(1.5, "cm")))

Run script to get hunter data

source('~/_code/colorado-dow/datasets/Colorado Elk Harvest Data.R', echo=F)
The working directory was changed to /Users/psarnow/_code/colorado-dow/datasets inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.

Table of the harvest data

head(COElkRifleAll)
  Unit Harvest.Antlered Hunters.Antlered Success.Antlered Season HuntCode Harvest.Antlerless Hunters.Antlerless Success.Antlerless Hunters.Either Success.Either Year
1    1                0                0               NA      1 EM001O1R                 NA                 NA                 NA             NA             NA 2006
2    2                0                0               NA      1 EM002O1R                 NA                 NA                 NA             NA             NA 2006
3  201                0                0               NA      1 EM201O1R                 NA                 NA                 NA             NA             NA 2006
4    3                0                0               NA      1 EM003O1R                 NA                 NA                 NA             NA             NA 2006
5  301                0                0               NA      1 EM301O1R                 NA                 NA                 NA             NA             NA 2006
6    4                0                0               NA      1 EM004O1R                 NA                 NA                 NA             NA             NA 2006

Run script to get draw data

source('~/_code/colorado-dow/datasets/Elk Drawing Summaries.R', echo=F)
Expected 26 pieces. Missing pieces filled with `NA` in 85 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].binding character and factor vector, coercing into character vectorExpected 26 pieces. Missing pieces filled with `NA` in 85 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].binding character and factor vector, coercing into character vectorExpected 26 pieces. Missing pieces filled with `NA` in 85 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].binding character and factor vector, coercing into character vectorExpected 26 pieces. Missing pieces filled with `NA` in 81 rows [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...].binding character and factor vector, coercing into character vectorThe working directory was changed to /Users/psarnow/_code/colorado-dow/datasets inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.

Table of the data

head(COElkDrawAll)
  HuntCode Orig_Quota Ttl_Chce_1 Chcs_Drawn    Sex Unit Season Year Draw_Success
1 EE003O1R       1075       2188       1075 Either    3      1 2006    0.4913163
2 EE003O4R        300        533        300 Either    3      4 2006    0.5628518
3 EE004O4R        150        235        150 Either    4      4 2006    0.6382979
4 EE005O4R         10         33         10 Either    5      4 2006    0.3030303
5 EE006O1R        100        168        100 Either    6      1 2006    0.5952381
6 EE006O4R         80         83         80 Either    6      4 2006    0.9638554

source geodata

source('~/_code/colorado-dow/datasets/Colorado GMUnit and Road data.R', echo=F)
OGR data source with driver: ESRI Shapefile 
Source: "/Users/psarnow/_code/colorado-dow/datasets/CPW_GMUBoundaries/BigGameGMUBoundaries03172015.shp", layer: "BigGameGMUBoundaries03172015"
with 185 features
It has 12 fields
Integer64 fields read as strings:  GMUID 
OGR data source with driver: ESRI Shapefile 
Source: "/Users/psarnow/_code/colorado-dow/datasets/ne_10m_roads/ne_10m_roads.shp", layer: "ne_10m_roads"
with 56601 features
It has 29 fields
Integer64 fields read as strings:  scalerank question 

Take a peak at the boundary data

head(Unitboundaries2)
  id      long      lat order  hole piece group COUNTY DEERDAU ELKDAU ANTDAU MOOSEDAU BEARDAU LIONDAU SQ_MILES    ACRES SHAPE_area SHAPE_len Unit
1  1 -109.0486 40.83476     1 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
2  1 -109.0472 40.83357     2 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
3  1 -109.0460 40.83295     3 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
4  1 -109.0449 40.83228     4 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
5  1 -109.0438 40.83204     5 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
6  1 -109.0423 40.83181     6 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99    B-15     L-1 127.2227 81422.53  329506619  100751.7    1

Set to predictive analytics directory

setwd("~/_code/colorado-dow/phase III - predictive analytics")

Organize data

Will start by grouping all of the seasons together, and modeling the number of hunters per Year and Unit

Group Draw results data by Year and Unit

COElkDraw <- summarise(group_by(COElkDrawAll,Year,Unit),
                       Quota = sum(Orig_Quota,na.rm = T),
                       Drawn = sum(Chcs_Drawn,na.rm = T))

Appropriate field classes for model training

COElkDraw$Year <- as.numeric(COElkDraw$Year)

Group Hunter data by Year and Unit

COElkHunters <- summarise(group_by(COElkRifleAll,Year,Unit),
                          Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHunters$Year <- as.numeric(COElkHunters$Year)

Join in Hunter and Draw data together

COElkHunters <- left_join(COElkHunters, COElkDraw, by = c("Year","Unit"))

Replace the draw data that don’t have entries with 0

COElkHunters$Drawn[is.na(COElkHunters$Drawn)] <- 0
COElkHunters$Quota[is.na(COElkHunters$Quota)] <- 0

Split into train and test sets. Will use 75% of the data to train on. Be sure to include each unit in the split. … so do the split for each unit, first make sure each Unit has at least three entries

COElkHunters <- mutate(group_by(COElkHunters,Unit),
                       numentries = n())
COElkHunters <- filter(COElkHunters, numentries >= 3)
COElkHunters$UnitYear <- paste(COElkHunters$Unit, COElkHunters$Year)
traindata <- COElkHunters %>% group_by(Unit) %>% sample_frac(size = .75, replace = F)
testdata <- COElkHunters[!COElkHunters$UnitYear %in% traindata$UnitYear,]
COElkHunters <- select(COElkHunters, -UnitYear, -numentries)
traindata <- select(traindata, -UnitYear, -numentries)
testdata <- select(testdata, -UnitYear, -numentries)

Save off for importing into AzureML

write.csv(COElkHunters,file = "~/_code/colorado-dow/datasets/COElkHunters.csv",row.names = F)

Data Visualization

notice that the number of hunters data is skewed.

ggplot(COElkHunters, aes(Hunters)) + 
  geom_density() +
  xlab("Hunters in Unit") +
  ylab("Number of Units") +
  theme(axis.text.y = element_blank()) +
  labs(title="Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

A general rule of thumb to consider is that skewed data whose ratio of the highest value to the lowest value is greater than 20 have significant skewness. Also, the skewness statistic can be used as a diagnostic. If the predictor distribution is roughly symmetric, the skewness values will be close to zero. As the distribution becomes more right skewed, the skewness statistic becomes larger. Similarly, as the distribution becomes more left skewed, the value becomes negative. Replacing the data with the log, square root, or inverse may help to remove the skew.

Example of how BoxCox can redistribute the data

preProcValues2 <- preProcess(as.data.frame(traindata), method = "BoxCox")
trainBC <- predict(preProcValues2, as.data.frame(traindata))
ggplot(trainBC, aes(Hunters)) + 
  geom_density() +
  xlab("BoxCox Hunters in Unit") +
  ylab("Number of Units") +
  theme(axis.text.y = element_blank()) +
  labs(title="BoxCox Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

caret has a preproccess function for correcting for skewness ‘BoxCox’, we will need to be sure to look at using this function in the training models.

COElkHarvest

top_two_models <- top_n(step1_all,2,-RMSE)$method

More Model Training Methods

Take the top two and determine some additonal methods to try by maximizing the Jaccard dissimilarity between sets of models

tag <- read.csv("tag_data.csv", row.names = 1)
tag <- as.matrix(tag)

Select only models for regression

regModels <- tag[tag[,"Regression"] == 1,]

all <- 1:nrow(regModels)
dissimilarmethods_all <- NULL
for (itoptwo in 1:2) {
  ## Seed the analysis with the model of interest
  start <- grep(top_two_models[itoptwo], rownames(regModels), fixed = TRUE)
  pool <- all[all != start]
  
  ## Select 4 model models by maximizing the Jaccard
  ## dissimilarity between sets of models
  nextMods <- maxDissim(regModels[start,,drop = FALSE], 
                        regModels[pool, ], 
                        method = "Jaccard",
                        n = 4)
  
  rownames(regModels)[c(nextMods)]
  
  dissimilarmethods <- rownames(regModels)[nextMods]
  dissimilarmethods <- str_extract(string = dissimilarmethods,pattern = "[:alnum:]+(?=\\))")
  dissimilarmethods_all <- c(dissimilarmethods_all,dissimilarmethods)
}

Now we have 8 more methods to try in the same manner

dissimilarmethods_all <- unique(dissimilarmethods_all)
dissimilarmethods_all
[1] "cubist"    "brnn"      "glm.nb"    "foba"      "ppr"       "rvmLinear"
for (imethod in dissimilarmethods_all) {
  step1 <- NULL
  start_timer <- now()[1]
  
  if (imethod == "lm") {
    controlmethod <- "repeatedcv"
  } else {controlmethod <- "adaptive_cv"}
  
  fitControl <- trainControl(
    method = controlmethod,
    # search = 'random',
    number = 4,
    repeats = 4,
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  HuntersModel_1 = train(Hunters ~ ., data = traindata,
                         method = imethod,
                         preProc = c("center","scale"), 
                         tuneLength = 15,
                         trControl = fitControl)
  
  HuntersModel_1
  
  # measure performance
  predictdata <- predict(HuntersModel_1, testdata)
  
  step1$method <- imethod
  step1$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
  step1$duration <- now()[1] - start_timer[1]
  step1 <- as.data.frame(step1)
  step1_all <- rbind(step1_all,step1)
}
step1_all
         method     RMSE       duration
RMSE         lm 165.0025  6.866598 secs
RMSE1 svmLinear 178.0942  4.950974 secs
RMSE2 svmRadial 134.0561 22.081016 secs
RMSE3       knn 733.8210 10.502875 secs
RMSE4    cubist 151.9572  3.057477 secs
RMSE5      kknn 132.0211  9.778734 secs
RMSE6    cubist 160.5464  1.027274 secs

Preprocessing on Top Modeling Methods

Now lets work on some refined tuning on the top methods Any valuable preprocessing steps?

preprocessfunctions <- c("BoxCox", "YeoJohnson", "expoTrans", "center", "scale", "range", "knnImpute", "bagImpute", "medianImpute", "pca", "ica", "spatialSign", "corr", "zv", "nzv")
topmethods <- top_n(step1_all,2,-RMSE)$method

fitControl <- trainControl(
  method = "adaptive_cv", #repeatedcv, 
  search = 'random',
  number = 10, #4
  repeats = 10, #10
  # classProbs = TRUE,
  # savePred = TRUE,
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

PPperformance_all <- NULL
PPperformance <- NULL
for (imethod in topmethods) {
  for (ipreprocess in preprocessfunctions) {
    registerDoSEQ()
    registerDoMC(cores = 6)
    
    PreProcessModel = train(Hunters ~ ., data = traindata,
                         method = imethod,
                         preProc = ipreprocess, 
                         #tuneLength = 10,
                         #tuneGrid = kknnTuneGrid,
                         trControl = fitControl)
    
    print(PreProcessModel)
    
    # check performance
    predictdata <- predict(PreProcessModel, testdata)
    
    PPperformance$method <- imethod
    PPperformance$preprocess <- ipreprocess
    PPperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
    PPperformance <- as.data.frame(PPperformance)
    PPperformance_all <- rbind(PPperformance_all,PPperformance)
  }
}
# Some of the models were loaded into AzureML and processed there.
# Output from AzureML
# [ModuleOutput]          method preprocess     RMSE
# [ModuleOutput] RMSE       kknn     BoxCox 130.7939
# [ModuleOutput] RMSE1      kknn YeoJohnson 130.9600
# [ModuleOutput] RMSE2      kknn     center 130.7331
# [ModuleOutput] RMSE3      kknn      scale 130.1818
# [ModuleOutput] RMSE4      kknn        pca 130.2071
# [ModuleOutput] RMSE5 svmRadial     BoxCox 154.0898
# [ModuleOutput] RMSE6 svmRadial YeoJohnson 169.9816
# [ModuleOutput] RMSE7 svmRadial     center 154.1891
# [ModuleOutput] RMSE8 svmRadial      scale 154.1000
# [ModuleOutput] RMSE9 svmRadial        pca 164.0881
# svmRadial and kknn don't perform better with any of the preprocessing functions in place
PPperformance_all
Error: object 'PPperformance_all' not found

Model Predictors

Now we can review the predictors, there are only a few fields so I will manually test performance while excluding each of them to monitor their importance. Some of our fields are instinctively required (Year, Unit)

Predictors <- c("Quota","Drawn")
Predictorperformance_all <- NULL
Predictorperformance <- NULL
for (imethod in topmethods) {
  for (ipredictor in Predictors) {
    registerDoSEQ()
    registerDoMC(cores = 6)
    
    PredictorModel = train(Hunters ~ ., data = select(traindata,-ipredictor),
                            method = imethod,
                            tuneLength = 15,
                            trControl = fitControl)
    
    print(PredictorModel)
    
    # check performance
    predictdata <- predict(PredictorModel, testdata)
    
    Predictorperformance$method <- imethod
    Predictorperformance$missing_predictor <- ipredictor
    Predictorperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
    Predictorperformance <- as.data.frame(Predictorperformance)
    Predictorperformance_all <- rbind(Predictorperformance_all,Predictorperformance)
  }
}
Predictorperformance_all
         method missing_predictor     RMSE
RMSE  svmRadial             Quota 152.5027
RMSE1 svmRadial             Drawn 153.7358
RMSE2      kknn             Quota 131.1298
RMSE3      kknn             Drawn 134.1082
RMSE4      kknn   Quota and Drawn 130.3965

svMRadial will perform better with all of the predictors, while kknn performs better with only Unit and Year fields

Use above information to test out various combinations of preprocessing and predictor sets

kknn

kknn without Quota and Drawn

fitControl <- trainControl(
  method = "adaptive_cv", #repeatedcv, 
  search = 'random',
  number = 10, #4
  repeats = 10, #10
  # classProbs = TRUE,
  # savePred = TRUE,
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

kknnModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                  method = "kknn",
                  tuneLength = 75,
                  trControl = fitControl)
kknnModel

Best RMSE

# not sure why caret is selecting parameters with higher RMSE, lets select manually
RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)
RSMEkknn
  kmax  distance    kernel     RMSE  Rsquared      MAE   RMSESD  RsquaredSD    MAESD .B
1  465 0.1073528 triweight 127.8562 0.9801797 82.07937 14.32044 0.006456134 6.309693 10

Model Tuning

run again with a tune grid

kknnTuneGrid <- data.frame(kmax = c(RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax),
                           distance = c(RSMEkknn$distance*.7,RSMEkknn$distance*.9,RSMEkknn$distance,RSMEkknn$distance*1.1,RSMEkknn$distance*1.3),
                           kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))

fitControl <- trainControl(
  method = "repeatedcv", #repeatedcv, 
  number = 10, #4
  repeats = 10, #10
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                  method = "kknn",
                  tuneGrid = kknnTuneGrid,
                  trControl = fitControl)
kknnGridModel

Best RMSE

RSMEkknn <- filter(kknnGridModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)

run again with a tune grid

kknnTuneGrid2 <- data.frame(kmax = c(RSMEkknn$kmax*.7,RSMEkknn$kmax*.9,RSMEkknn$kmax,RSMEkknn$kmax*1.1,RSMEkknn$kmax*1.3),
                           distance = c(RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance),
                           kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel2 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                      method = "kknn",
                      tuneGrid = kknnTuneGrid2,
                      trControl = fitControl)
kknnGridModel2

One more time on final parameter (kernel) Best RMSE

RSMEkknn <- filter(kknnGridModel2$results, RMSE == min(RMSE))[1,]
kernels <- levels(kknnModel$results$kernel)

run again with a tune grid

kknnTuneGrid3 <- data.frame(kmax = rep(465.0,8),
                            distance = rep(0.1395586,8),
                            kernel = kernels)

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel3 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                       method = "kknn",
                       tuneGrid = kknnTuneGrid3,
                       trControl = fitControl)
kknnGridModel3
RSMEkknn <- filter(kknnGridModel3$results, RMSE == min(RMSE))

Best RMSE for kknn thus far

RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))

Work thru some resampling methods with best kknn params

kknnTuneGrid4 <- data.frame(kmax = RSMEkknn$kmax,
                            distance = RSMEkknn$distance,
                            kernel = as.character(RSMEkknn$kernel))

trainmethods <- c("boot", "boot632", "optimism_boot", "boot_all", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
  trainmethodperformance <- NULL
  fitControl <- trainControl(
    method = itrainmethod,
    number = 10, #4
    repeats = 10, #10
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  kknnTrainModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                         method = "kknn",
                         tuneGrid = kknnTuneGrid4,
                         trControl = fitControl)
  
  print(kknnTrainModel)
  trainmethodperformance <- filter(kknnTrainModel$results, RMSE == min(RMSE))
  trainmethodperformance$trainmethod <- itrainmethod
  trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
fitControl <- trainControl(
  method = "optimism_boot",
  number = 10, #4
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

kknnFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
                       method = "kknn",
                       tuneGrid = kknnTuneGrid4,
                       trControl = fitControl)

save off for future loading

save(kknnFinalTrainModel, file = "~/_code/colorado-dow/datasets/kknnFinalTrainModel.RData")

Model Testing

back to train vs test data for one more performance measure and chart… even though for future data we will use the final trained model

kknnTrainModel = train(Hunters ~ ., data = traindata,
                            method = "kknn",
                            tuneGrid = kknnTuneGrid4,
                            trControl = fitControl)

check performance

predictdata <- predict(kknnTrainModel, testdata)

postResample(pred = predictdata, obs = testdata$Hunters)

Chart performance of predicted

chartperformance <- data.frame(predicted = predictdata, observed = testdata$Hunters)
ggplot(chartperformance, aes(predicted,observed)) +
  geom_point() +
  labs(title="Performance of Number of Hunters Prediction", caption="source: cpw.state.co.us")

SVM

Output from AzureML [ModuleOutput] Support Vector Machines with Radial Basis Function Kernel [ModuleOutput] [ModuleOutput] 1540 samples [ModuleOutput] 4 predictors [ModuleOutput] [ModuleOutput] No pre-processing [ModuleOutput] Resampling: Cross-Validated (10 fold, repeated 10 times) [ModuleOutput] [ModuleOutput] Summary of sample sizes: 1386, 1386, 1387, 1387, 1385, 1385, … [ModuleOutput] [ModuleOutput] Resampling results across tuning parameters: [ModuleOutput] [ModuleOutput] C RMSE Rsquared RMSE SD Rsquared SD [ModuleOutput] 0.25 276 0.946 30.6 0.00822
[ModuleOutput] 0.5 203 0.96 21.4 0.00754
[ModuleOutput] 1 180 0.965 17.7 0.00671
[ModuleOutput] 2 168 0.969 15.7 0.00614
[ModuleOutput] 4 158 0.972 14.9 0.0055
[ModuleOutput] 8 150 0.975 14.7 0.00506
[ModuleOutput] 16 146 0.976 14.7 0.00481
[ModuleOutput] 32 144 0.976 14.7 0.00477
[ModuleOutput] 64 143 0.977 14.6 0.00474
[ModuleOutput] 128 140 0.977 14.3 0.00469
[ModuleOutput] 256 139 0.978 15 0.00485
[ModuleOutput] 512 137 0.978 14.9 0.00484
[ModuleOutput] 1020 136 0.979 15.3 0.00494
[ModuleOutput] 2050 135 0.979 15.6 0.00513
[ModuleOutput] 4100 136 0.979 15.5 0.0051
[ModuleOutput] 8190 137 0.978 15.7 0.00518
[ModuleOutput] 16400 139 0.978 16.5 0.00551
[ModuleOutput] 32800 141 0.977 17.6 0.006
[ModuleOutput] 65500 145 0.976 19.4 0.00659
[ModuleOutput] 131000 151 0.974 20.8 0.00718
[ModuleOutput] 262000 161 0.97 27.2 0.0104
[ModuleOutput] 524000 478 0.8 325 0.172
[ModuleOutput] 1050000 1180 0.5 1010 0.204
[ModuleOutput] 2100000 3240 0.148 2260 0.117
[ModuleOutput] 4190000 6000 0.0604 6400 0.0527
[ModuleOutput] [ModuleOutput] Tuning parameter ‘sigma’ was held constant at a value of 0.0037653 [ModuleOutput] RMSE was used to select the optimal model using the smallest value. [ModuleOutput] The final values used for the model were sigma = 0.00377 and C = 2048.

run again with a tune grid

svmRadTuneGrid <- data.frame(.sigma = c(0.0037653,0.0037653,0.0037653,0.0037653,0.0037653),
                            .C = c(2048*.7,2048*.9,2048,2048*1.1,2048*1.3))

fitControl <- trainControl(
  method = "repeatedcv", #repeatedcv, 
  number = 10, #4
  repeats = 10, #10
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

svmRadGridModel = train(Hunters ~ ., data = COElkHunters,
                      method = "svmRadial",
                      tuneGrid = svmRadTuneGrid,
                      trControl = fitControl)
svmRadGridModel

Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually

RSMEsvmRad <- filter(svmRadGridModel$results, RMSE == min(RMSE))

run again with a tune grid

svmRadTuneGrid2 <- data.frame(.sigma = c(RSMEsvmRad$sigma*.7,RSMEsvmRad$sigma*.9,RSMEsvmRad$sigma,RSMEsvmRad$sigma*1.1,RSMEsvmRad$sigma*1.3),
                             .C = c(RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C))

registerDoSEQ()
registerDoMC(cores = 6)

svmRadGridModel2 = train(Hunters ~ ., data = COElkHunters,
                        method = "svmRadial",
                        tuneGrid = svmRadTuneGrid2,
                        trControl = fitControl)
svmRadGridModel2
RSMEsvmRad <- filter(svmRadGridModel2$results, RMSE == min(RMSE))

Work thru some resampling methods with best kknn params

svmRadTuneGrid3 <- data.frame(.sigma = RSMEsvmRad$sigma,
                            .C = RSMEsvmRad$C)

trainmethods <- c("boot", "boot632", "optimism_boot", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
  trainmethodperformance <- NULL
  fitControl <- trainControl(
    method = itrainmethod,
    number = 10, #4
    repeats = 10, #10
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  svmRadTrainModel = train(Hunters ~ ., data = COElkHunters,
                         method = "svmRadial",
                         tuneGrid = svmRadTuneGrid3,
                         trControl = fitControl)
  
  print(svmRadTrainModel)
  trainmethodperformance <- filter(svmRadTrainModel$results, RMSE == min(RMSE))
  trainmethodperformance$trainmethod <- itrainmethod
  trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
fitControl <- trainControl(
  method = "optimism_boot",
  number = 10, #4
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

svmRadFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
                            method = "svmRadial",
                            tuneGrid = svmRadTuneGrid3,
                            trControl = fitControl)

save off for future loading

save(svmRadFinalTrainModel, file = "~/_code/colorado-dow/datasets/svmRadFinalTrainModel.RData")

back to train vs test data for one more performance measure and chart… even though for future data we will use the final trained model

svmRadTrainModel = train(Hunters ~ ., data = traindata,
                       method = "svmRadial",
                       tuneGrid = svmRadTuneGrid3,
                       trControl = fitControl)

check performance

predictdata <- predict(svmRadTrainModel, testdata)
postResample(pred = predictdata, obs = testdata$Hunters)

Chart performance of predicted

chartperformance <- data.frame(predicted = predictdata, observed = testdata$Hunters)
ggplot(chartperformance, aes(predicted,observed)) +
  geom_point() +
  labs(title="Performance of Number of Hunters Prediction", caption="source: cpw.state.co.us")

kknn performed better than svmRadial RMSE=130 vs 154

FinalHuntersmodel <- kknnFinalTrainModel
# FinalHuntersmodel <- svmRadFinalTrainModel
save(FinalHuntersmodel, file = "~/_code/colorado-dow/datasets/FinalHuntersmodel.RData")

Use the 2018 Draw data to predict the number of hunters in 2018

COElkHunters2018 <- as.data.frame(unique(COElkHunters$Unit))
colnames(COElkHunters2018) <- "Unit"
COElkHunters2018$Year <- 2018
# Draw data for 2018
COElkDraw2018 <- filter(COElkDraw, Year == 2018)
# A left join will autofill missing draw data with NAs, but will retain the full list of Units
COElkHunters2018 <- left_join(COElkHunters2018,COElkDraw2018)
Joining, by = c("Unit", "Year")
Column `Unit` joining factor and character vector, coercing into character vector
# Replace the draw data that don't have entries with 0
COElkHunters2018$Drawn[is.na(COElkHunters2018$Drawn)] <- 0
COElkHunters2018$Quota[is.na(COElkHunters2018$Quota)] <- 0
COElkHunters2018 <- COElkHunters2018[, colnames(COElkHunters2018) %in% c("Unit",FinalHuntersmodel$coefnames)]
COElkHunters2018$Hunters <- round(predict(FinalHuntersmodel, COElkHunters2018))
COElkHunters2018$Hunters[COElkHunters2018$Hunters<0] <- 0

Save off so we don’t have to recreate the model everytime we want the results

save(COElkHunters2018,file="COElkHunters2018.RData")

Total Elk Harvest

Statewide

Group seasons

COElkHuntersStatewide <- summarise(group_by(COElkRifleAll,Year,Unit),
                                  Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHunters2018b <- COElkHunters2018
# COElkHunters2018b$Year <- as.character(COElkHunters2018b$Year)

# Join 2018 to historic data
COElkHuntersAll <- rbind.fill(COElkHuntersStatewide,COElkHunters2018b)

# Group Units
COElkHuntersStatewide <- summarise(group_by(COElkHuntersAll,Year),
                                   Hunters = sum(Hunters))
ggplot(COElkHuntersStatewide, aes(Year,Hunters)) +
  geom_bar(stat="identity",fill=ggthemes_data$hc$palettes$default[2]) +
  coord_cartesian(ylim = c(130000,155000)) +
  labs(title="Statewide Elk Hunters", caption="source: cpw.state.co.us")

TODO commentary


Hunters by Unit

I’d like to know where the hunters are distributed across the state.

Next year’s data

Year2018 <- filter(COElkHuntersAll, Year == "2018")
HunterstoPlot <- left_join(Unitboundaries2,Year2018, by=c("Unit"))
ggplot(HunterstoPlot, aes(long, lat, group = group)) + 
  geom_polygon(aes(fill = Hunters),colour = "grey50", size = .2) + #Unit boundaries
  geom_path(data = COroads,aes(x = long, y = lat, group = group), color="#3878C7",size=2) + #Roads
  geom_text(data=data_centroids,aes(x=longitude,y=latitude,label = Unit),size=3) + #Unit labels
  scale_fill_distiller(palette = "Oranges",direction = 1,na.value = 'light grey') +
  xlab("") + ylab("") +
  labs(title="Predicted 2018 Colorado Elk Hunters", caption="source: cpw.state.co.us")

TODO - commentary


Number of Hunters Rank of the Units

Would also be beneficial to rank each unit so I can reference later. In this case average the number of hunters of the last few years

HunterRank2018 <- filter(COElkHuntersAll, as.numeric(Year) == 2018)
HunterRank2018 <- summarise(group_by(HunterRank2018,Unit),
                             Hunters = mean(Hunters,na.rm = T))
HunterRank2018$HuntersRank = rank(-HunterRank2018$Hunters)

HunterRank2018 <- filter(HunterRank2018, HuntersRank <= 50) # top 50 units
# In order for the chart to retain the order of the rows, the X axis variable (i.e. the categories) has to be converted into a factor.
HunterRank2018 <- HunterRank2018[order(-HunterRank2018$Hunters), ]  # sort
HunterRank2018$Unit <- factor(HunterRank2018$Unit, levels = HunterRank2018$Unit)  # to retain the order in plot.

Lollipop Chart

ggplot(HunterRank2018, aes(x=Unit, y=Hunters)) + 
  geom_point(size=3) + 
  geom_segment(aes(x=Unit, 
                   xend=Unit, 
                   y=0, 
                   yend=Hunters)) + 
  labs(title="Predicted Elk Hunters 2018\nTop 50 Units", subtitle="Hunters by Unit", caption="source: cpw.state.co.us")

TODO - commentary


Conclusion

TODO

LS0tCnRpdGxlOiAiUHJlZGljdCBOdW1iZXIgb2YgRnV0dXJlIEVsayBIdW50ZXJzIgphdXRob3I6ICJQaWVycmUgU2Fybm93IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDYKICAgIHRoZW1lOiB5ZXRpCiAgICBoaWdodGxpZ2h0OiBkZWZhdWx0CiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUKLS0tCgoKKioqCiMjIERlc2NyaXB0aW9uClVzZSBoaXN0b3JpY2FsIGRyYXcgcmVzdWx0cywgYW5kIG51bWJlciBvZiBodW50ZXJzIHRvIHRyYWluIGEgbW9kZWwgd2UgY2FuIHVzZSB0byAKcHJlZGljdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgaW4gZnV0dXJlIHllYXJzLgoKVE9ETyAtIEluY2x1ZGUgb3RoZXIgcG90ZW50aWFsIGlucHV0cyB0aGF0IGNvdWxkIGltcGFjdCBob3cgbWFueSBodW50ZXJzIGdldCBhIGxpY2Vuc2UKYW5kIHNob3cgdXAuIFRob3NlIGNvdWxkIGluY2x1ZGUgZWNvbm9taWMgaW5kaWNhdG9ycywgYW5kIGNvc3RzIGFzc29jaWF0ZWQgd2l0aCBodW50aW5nCih0cmFuc3BvcnRhdGlvbiwgbG9kZ2luZykuCgoqX19OT1RJQ0VfXyB0aGF0IEkgYW0gb25seSBsb29raW5nIGF0IHRoZSBnZW5lcmFsIHJpZmxlIGh1bnRpbmcgc2Vhc29ucyBvbiBwdWJsaWMgbGFuZC4gVGhlcmUgYXJlIGFsc28gCmh1bnRlcnMgZm9yIEFyY2hlcnksIE11enpsZWxvYWRlciwgUHJpdmF0ZSBMYW5kLCBSYW5jaGluZyBmb3IgV2lsZGxpZmUsIGV0Yy4qCgoqKioKIyMgU2V0dXAKTG9hZCByZXF1aXJlZCBsaWJyYXJpZXMgZm9yIHdyYW5nbGluZyBkYXRhLCBjaGFydGluZywgYW5kIG1hcHBpbmcKYGBge3J9CmxpYnJhcnkocGx5cixxdWlldGx5ID0gVCkgIyBkYXRhIHdyYW5nbGluZwpsaWJyYXJ5KGRwbHlyLHF1aWV0bHkgPSBUKSAjIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZ2dwbG90MiwgcXVpZXRseSA9IFQpICMgY2hhcnRpbmcKbGlicmFyeShnZ3RoZW1lcyxxdWlldGx5ID0gVCkgIyBzbyBJIGNhbiBhZGQgdGhlIGhpZ2hjaGFydHMgdGhlbWUgYW5kIHBhbGV0dGUKbGlicmFyeShzY2FsZXMscXVpZXRseSA9IFQpICMgdG8gbG9hZCB0aGUgcGVyY2VudCBmdW5jdGlvbiB3aGVuIGxhYmVsaW5nIHBsb3RzCmxpYnJhcnkoY2FyZXQscXVpZXRseSA9IFQpICMgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gdHJhaW5pbmcKbGlicmFyeShmb3JlYWNoLHF1aWV0bHkgPSBUKSAjIHBhcmFsbGVsIHByb2Nlc3NpbmcgdG8gc3BlZWQgdXAgdGhlIG1vZGVsIHRyYWluaW5nCmxpYnJhcnkoZG9NQyxxdWlldGx5ID0gVCkgIyBwYXJhbGxlbCBwcm9jZXNzaW5nIHRvIHNwZWVkIHVwIHRoZSBtb2RlbCB0cmFpbmluZwpsaWJyYXJ5KGx1YnJpZGF0ZSxxdWlldGx5ID0gVCkgIyBmb3IgdGltaW5nIG1vZGVscwpgYGAKClNldCBvdXIgcHJlZmVycmVkIGNoYXJ0aW5nIHRoZW1lCmBgYHtyfQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpK3RoZW1lX2hjKCkrdGhlbWUobGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMS41LCAiY20iKSkpCmBgYCAKClJ1biBzY3JpcHQgdG8gZ2V0IGh1bnRlciBkYXRhCmBgYHtyfQpzb3VyY2UoJ34vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NvbG9yYWRvIEVsayBIYXJ2ZXN0IERhdGEuUicsIGVjaG89RikKYGBgCgpUYWJsZSBvZiB0aGUgaGFydmVzdCBkYXRhCmBgYHtyfQpoZWFkKENPRWxrUmlmbGVBbGwpCmBgYAoKClJ1biBzY3JpcHQgdG8gZ2V0IGRyYXcgZGF0YQpgYGB7cn0Kc291cmNlKCd+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9FbGsgRHJhd2luZyBTdW1tYXJpZXMuUicsIGVjaG89RikKYGBgCgpUYWJsZSBvZiB0aGUgZGF0YQpgYGB7cn0KaGVhZChDT0Vsa0RyYXdBbGwpCmBgYAoKc291cmNlIGdlb2RhdGEKYGBge3J9CnNvdXJjZSgnfi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvQ29sb3JhZG8gR01Vbml0IGFuZCBSb2FkIGRhdGEuUicsIGVjaG89RikKYGBgCgpUYWtlIGEgcGVhayBhdCB0aGUgYm91bmRhcnkgZGF0YQpgYGB7cn0KaGVhZChVbml0Ym91bmRhcmllczIpCmBgYAoKU2V0IHRvIHByZWRpY3RpdmUgYW5hbHl0aWNzIGRpcmVjdG9yeQpgYGB7cn0Kc2V0d2QoIn4vX2NvZGUvY29sb3JhZG8tZG93L3BoYXNlIElJSSAtIHByZWRpY3RpdmUgYW5hbHl0aWNzIikKYGBgCgojIyMgT3JnYW5pemUgZGF0YQpXaWxsIHN0YXJ0IGJ5IGdyb3VwaW5nIGFsbCBvZiB0aGUgc2Vhc29ucyB0b2dldGhlciwgYW5kIG1vZGVsaW5nIHRoZSBudW1iZXIgb2YgaHVudGVycyBwZXIgWWVhciBhbmQgVW5pdAoKR3JvdXAgRHJhdyByZXN1bHRzIGRhdGEgYnkgWWVhciBhbmQgVW5pdApgYGB7cn0KQ09FbGtEcmF3IDwtIHN1bW1hcmlzZShncm91cF9ieShDT0Vsa0RyYXdBbGwsWWVhcixVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBRdW90YSA9IHN1bShPcmlnX1F1b3RhLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgRHJhd24gPSBzdW0oQ2hjc19EcmF3bixuYS5ybSA9IFQpKQpgYGAKCkFwcHJvcHJpYXRlIGZpZWxkIGNsYXNzZXMgZm9yIG1vZGVsIHRyYWluaW5nCmBgYHtyfQpDT0Vsa0RyYXckWWVhciA8LSBhcy5udW1lcmljKENPRWxrRHJhdyRZZWFyKQpgYGAKCkdyb3VwIEh1bnRlciBkYXRhIGJ5IFllYXIgYW5kIFVuaXQKYGBge3J9CkNPRWxrSHVudGVycyA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIEh1bnRlcnMgPSBzdW0oYyhIdW50ZXJzLkFudGxlcmVkLEh1bnRlcnMuQW50bGVybGVzcyxIdW50ZXJzLkVpdGhlciksbmEucm0gPSBUKSkKCkNPRWxrSHVudGVycyRZZWFyIDwtIGFzLm51bWVyaWMoQ09FbGtIdW50ZXJzJFllYXIpCmBgYAoKSm9pbiBpbiBIdW50ZXIgYW5kIERyYXcgZGF0YSB0b2dldGhlcgpgYGB7cn0KQ09FbGtIdW50ZXJzIDwtIGxlZnRfam9pbihDT0Vsa0h1bnRlcnMsIENPRWxrRHJhdywgYnkgPSBjKCJZZWFyIiwiVW5pdCIpKQpgYGAKClJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKYGBge3J9CkNPRWxrSHVudGVycyREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMkRHJhd24pXSA8LSAwCkNPRWxrSHVudGVycyRRdW90YVtpcy5uYShDT0Vsa0h1bnRlcnMkUXVvdGEpXSA8LSAwCmBgYAoKU3BsaXQgaW50byB0cmFpbiBhbmQgdGVzdCBzZXRzLiBXaWxsIHVzZSA3NSUgb2YgdGhlIGRhdGEgdG8gdHJhaW4gb24uIEJlIHN1cmUgdG8gaW5jbHVkZQplYWNoIHVuaXQgaW4gdGhlIHNwbGl0LiAuLi4gc28gZG8gdGhlIHNwbGl0IGZvciBlYWNoIHVuaXQsIGZpcnN0IG1ha2Ugc3VyZSBlYWNoIFVuaXQgaGFzCmF0IGxlYXN0IHRocmVlIGVudHJpZXMKCmBgYHtyfQpDT0Vsa0h1bnRlcnMgPC0gbXV0YXRlKGdyb3VwX2J5KENPRWxrSHVudGVycyxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBudW1lbnRyaWVzID0gbigpKQpDT0Vsa0h1bnRlcnMgPC0gZmlsdGVyKENPRWxrSHVudGVycywgbnVtZW50cmllcyA+PSAzKQpDT0Vsa0h1bnRlcnMkVW5pdFllYXIgPC0gcGFzdGUoQ09FbGtIdW50ZXJzJFVuaXQsIENPRWxrSHVudGVycyRZZWFyKQoKdHJhaW5kYXRhIDwtIENPRWxrSHVudGVycyAlPiUgZ3JvdXBfYnkoVW5pdCkgJT4lIHNhbXBsZV9mcmFjKHNpemUgPSAuNzUsIHJlcGxhY2UgPSBGKQp0ZXN0ZGF0YSA8LSBDT0Vsa0h1bnRlcnNbIUNPRWxrSHVudGVycyRVbml0WWVhciAlaW4lIHRyYWluZGF0YSRVbml0WWVhcixdCgpDT0Vsa0h1bnRlcnMgPC0gc2VsZWN0KENPRWxrSHVudGVycywgLVVuaXRZZWFyLCAtbnVtZW50cmllcykKCnRyYWluZGF0YSA8LSBzZWxlY3QodHJhaW5kYXRhLCAtVW5pdFllYXIsIC1udW1lbnRyaWVzKQp0ZXN0ZGF0YSA8LSBzZWxlY3QodGVzdGRhdGEsIC1Vbml0WWVhciwgLW51bWVudHJpZXMpCmBgYAoKU2F2ZSBvZmYgZm9yIGltcG9ydGluZyBpbnRvIEF6dXJlTUwKYGBge3J9CndyaXRlLmNzdihDT0Vsa0h1bnRlcnMsZmlsZSA9ICJ+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9DT0Vsa0h1bnRlcnMuY3N2Iixyb3cubmFtZXMgPSBGKQpgYGAKIyMjIERhdGEgVmlzdWFsaXphdGlvbgpub3RpY2UgdGhhdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgZGF0YSBpcyBza2V3ZWQuCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KENPRWxrSHVudGVycywgYWVzKEh1bnRlcnMpKSArIAogIGdlb21fZGVuc2l0eSgpICsKICB4bGFiKCJIdW50ZXJzIGluIFVuaXQiKSArCiAgeWxhYigiTnVtYmVyIG9mIFVuaXRzIikgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIEh1bnRlcnMgaW4gZWFjaCBVbml0Iiwgc3VidGl0bGU9IjIwMDYtMjAxNyIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKCmBgYAoKCkEgZ2VuZXJhbCBydWxlIG9mIHRodW1iIHRvIGNvbnNpZGVyIGlzIHRoYXQgc2tld2VkIGRhdGEgd2hvc2UgcmF0aW8gb2YgdGhlIGhpZ2hlc3QgdmFsdWUgdG8gdGhlIApsb3dlc3QgdmFsdWUgaXMgZ3JlYXRlciB0aGFuIDIwIGhhdmUgc2lnbmlmaWNhbnQgc2tld25lc3MuIEFsc28sIHRoZSBza2V3bmVzcyBzdGF0aXN0aWMgY2FuIGJlIAp1c2VkIGFzIGEgZGlhZ25vc3RpYy4gSWYgdGhlIHByZWRpY3RvciBkaXN0cmlidXRpb24gaXMgcm91Z2hseSBzeW1tZXRyaWMsIHRoZSBza2V3bmVzcyB2YWx1ZXMgCndpbGwgYmUgY2xvc2UgdG8gemVyby4gQXMgdGhlIGRpc3RyaWJ1dGlvbiBiZWNvbWVzIG1vcmUgcmlnaHQgc2tld2VkLCB0aGUgc2tld25lc3Mgc3RhdGlzdGljIApiZWNvbWVzIGxhcmdlci4gU2ltaWxhcmx5LCBhcyB0aGUgZGlzdHJpYnV0aW9uIGJlY29tZXMgbW9yZSBsZWZ0IHNrZXdlZCwgdGhlIHZhbHVlIGJlY29tZXMgbmVnYXRpdmUuClJlcGxhY2luZyB0aGUgZGF0YSB3aXRoIHRoZSBsb2csIHNxdWFyZSByb290LCBvciBpbnZlcnNlIG1heSBoZWxwIHRvIHJlbW92ZSB0aGUgc2tldy4KCkV4YW1wbGUgb2YgaG93IEJveENveCBjYW4gcmVkaXN0cmlidXRlIHRoZSBkYXRhCmBgYHtyfQpwcmVQcm9jVmFsdWVzMiA8LSBwcmVQcm9jZXNzKGFzLmRhdGEuZnJhbWUodHJhaW5kYXRhKSwgbWV0aG9kID0gIkJveENveCIpCnRyYWluQkMgPC0gcHJlZGljdChwcmVQcm9jVmFsdWVzMiwgYXMuZGF0YS5mcmFtZSh0cmFpbmRhdGEpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KHRyYWluQkMsIGFlcyhIdW50ZXJzKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArCiAgeGxhYigiQm94Q294IEh1bnRlcnMgaW4gVW5pdCIpICsKICB5bGFiKCJOdW1iZXIgb2YgVW5pdHMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHRpdGxlPSJCb3hDb3ggRGlzdHJpYnV0aW9uIG9mIEh1bnRlcnMgaW4gZWFjaCBVbml0Iiwgc3VidGl0bGU9IjIwMDYtMjAxNyIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCmNhcmV0IGhhcyBhIHByZXByb2NjZXNzIGZ1bmN0aW9uIGZvciBjb3JyZWN0aW5nIGZvciBza2V3bmVzcyAnQm94Q294Jywgd2Ugd2lsbCBuZWVkIHRvIGJlIHN1cmUgdG8KbG9vayBhdCB1c2luZyB0aGlzIGZ1bmN0aW9uIGluIHRoZSB0cmFpbmluZyBtb2RlbHMuCgpDT0Vsa0hhcnZlc3QKYGBge3J9CnRvcF90d29fbW9kZWxzIDwtIHRvcF9uKHN0ZXAxX2FsbCwyLC1STVNFKSRtZXRob2QKYGBgCiMjIyBNb3JlIE1vZGVsIFRyYWluaW5nIE1ldGhvZHMKClRha2UgdGhlIHRvcCB0d28gYW5kIGRldGVybWluZSBzb21lIGFkZGl0b25hbCBtZXRob2RzIHRvIHRyeSBieSBtYXhpbWl6aW5nIHRoZSBKYWNjYXJkCmRpc3NpbWlsYXJpdHkgYmV0d2VlbiBzZXRzIG9mIG1vZGVscwpgYGB7cn0KdGFnIDwtIHJlYWQuY3N2KCJ0YWdfZGF0YS5jc3YiLCByb3cubmFtZXMgPSAxKQp0YWcgPC0gYXMubWF0cml4KHRhZykKYGBgCgpTZWxlY3Qgb25seSBtb2RlbHMgZm9yIHJlZ3Jlc3Npb24KYGBge3J9CnJlZ01vZGVscyA8LSB0YWdbdGFnWywiUmVncmVzc2lvbiJdID09IDEsXQoKYWxsIDwtIDE6bnJvdyhyZWdNb2RlbHMpCmRpc3NpbWlsYXJtZXRob2RzX2FsbCA8LSBOVUxMCmZvciAoaXRvcHR3byBpbiAxOjIpIHsKICAjIyBTZWVkIHRoZSBhbmFseXNpcyB3aXRoIHRoZSBtb2RlbCBvZiBpbnRlcmVzdAogIHN0YXJ0IDwtIGdyZXAodG9wX3R3b19tb2RlbHNbaXRvcHR3b10sIHJvd25hbWVzKHJlZ01vZGVscyksIGZpeGVkID0gVFJVRSkKICBwb29sIDwtIGFsbFthbGwgIT0gc3RhcnRdCiAgCiAgIyMgU2VsZWN0IDQgbW9kZWwgbW9kZWxzIGJ5IG1heGltaXppbmcgdGhlIEphY2NhcmQKICAjIyBkaXNzaW1pbGFyaXR5IGJldHdlZW4gc2V0cyBvZiBtb2RlbHMKICBuZXh0TW9kcyA8LSBtYXhEaXNzaW0ocmVnTW9kZWxzW3N0YXJ0LCxkcm9wID0gRkFMU0VdLCAKICAgICAgICAgICAgICAgICAgICAgICAgcmVnTW9kZWxzW3Bvb2wsIF0sIAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiSmFjY2FyZCIsCiAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA0KQogIAogIHJvd25hbWVzKHJlZ01vZGVscylbYyhuZXh0TW9kcyldCiAgCiAgZGlzc2ltaWxhcm1ldGhvZHMgPC0gcm93bmFtZXMocmVnTW9kZWxzKVtuZXh0TW9kc10KICBkaXNzaW1pbGFybWV0aG9kcyA8LSBzdHJfZXh0cmFjdChzdHJpbmcgPSBkaXNzaW1pbGFybWV0aG9kcyxwYXR0ZXJuID0gIls6YWxudW06XSsoPz1cXCkpIikKICBkaXNzaW1pbGFybWV0aG9kc19hbGwgPC0gYyhkaXNzaW1pbGFybWV0aG9kc19hbGwsZGlzc2ltaWxhcm1ldGhvZHMpCn0KYGBgCgpOb3cgd2UgaGF2ZSA4IG1vcmUgbWV0aG9kcyB0byB0cnkgaW4gdGhlIHNhbWUgbWFubmVyCmBgYHtyfQpkaXNzaW1pbGFybWV0aG9kc19hbGwgPC0gdW5pcXVlKGRpc3NpbWlsYXJtZXRob2RzX2FsbCkKZGlzc2ltaWxhcm1ldGhvZHNfYWxsCmBgYApgYGB7cn0KZm9yIChpbWV0aG9kIGluIGRpc3NpbWlsYXJtZXRob2RzX2FsbCkgewogIHN0ZXAxIDwtIE5VTEwKICBzdGFydF90aW1lciA8LSBub3coKVsxXQogIAogIGlmIChpbWV0aG9kID09ICJsbSIpIHsKICAgIGNvbnRyb2xtZXRob2QgPC0gInJlcGVhdGVkY3YiCiAgfSBlbHNlIHtjb250cm9sbWV0aG9kIDwtICJhZGFwdGl2ZV9jdiJ9CiAgCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBjb250cm9sbWV0aG9kLAogICAgIyBzZWFyY2ggPSAncmFuZG9tJywKICAgIG51bWJlciA9IDQsCiAgICByZXBlYXRzID0gNCwKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBIdW50ZXJzTW9kZWxfMSA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gaW1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxNSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCiAgCiAgSHVudGVyc01vZGVsXzEKICAKICAjIG1lYXN1cmUgcGVyZm9ybWFuY2UKICBwcmVkaWN0ZGF0YSA8LSBwcmVkaWN0KEh1bnRlcnNNb2RlbF8xLCB0ZXN0ZGF0YSkKICAKICBzdGVwMSRtZXRob2QgPC0gaW1ldGhvZAogIHN0ZXAxJFJNU0UgPC0gcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycylbMV0KICBzdGVwMSRkdXJhdGlvbiA8LSBub3coKVsxXSAtIHN0YXJ0X3RpbWVyWzFdCiAgc3RlcDEgPC0gYXMuZGF0YS5mcmFtZShzdGVwMSkKICBzdGVwMV9hbGwgPC0gcmJpbmQoc3RlcDFfYWxsLHN0ZXAxKQp9CmBgYAoKYGBge3J9CnN0ZXAxX2FsbApgYGAKCiMjIyBQcmVwcm9jZXNzaW5nIG9uIFRvcCBNb2RlbGluZyBNZXRob2RzCk5vdyBsZXRzIHdvcmsgb24gc29tZSByZWZpbmVkIHR1bmluZyBvbiB0aGUgdG9wIG1ldGhvZHMKQW55IHZhbHVhYmxlIHByZXByb2Nlc3Npbmcgc3RlcHM/CmBgYHtyfQpwcmVwcm9jZXNzZnVuY3Rpb25zIDwtIGMoIkJveENveCIsICJZZW9Kb2huc29uIiwgImV4cG9UcmFucyIsICJjZW50ZXIiLCAic2NhbGUiLCAicmFuZ2UiLCAia25uSW1wdXRlIiwgImJhZ0ltcHV0ZSIsICJtZWRpYW5JbXB1dGUiLCAicGNhIiwgImljYSIsICJzcGF0aWFsU2lnbiIsICJjb3JyIiwgInp2IiwgIm56diIpCnRvcG1ldGhvZHMgPC0gdG9wX24oc3RlcDFfYWxsLDIsLVJNU0UpJG1ldGhvZAoKZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gImFkYXB0aXZlX2N2IiwgI3JlcGVhdGVkY3YsIAogIHNlYXJjaCA9ICdyYW5kb20nLAogIG51bWJlciA9IDEwLCAjNAogIHJlcGVhdHMgPSAxMCwgIzEwCiAgIyBjbGFzc1Byb2JzID0gVFJVRSwKICAjIHNhdmVQcmVkID0gVFJVRSwKICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICBzdW1tYXJ5RnVuY3Rpb24gPSBkZWZhdWx0U3VtbWFyeSkKClBQcGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKUFBwZXJmb3JtYW5jZSA8LSBOVUxMCmZvciAoaW1ldGhvZCBpbiB0b3BtZXRob2RzKSB7CiAgZm9yIChpcHJlcHJvY2VzcyBpbiBwcmVwcm9jZXNzZnVuY3Rpb25zKSB7CiAgICByZWdpc3RlckRvU0VRKCkKICAgIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgICAKICAgIFByZVByb2Nlc3NNb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gaW1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBpcHJlcHJvY2VzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAjdHVuZUxlbmd0aCA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgI3R1bmVHcmlkID0ga2tublR1bmVHcmlkLAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAgIAogICAgcHJpbnQoUHJlUHJvY2Vzc01vZGVsKQogICAgCiAgICAjIGNoZWNrIHBlcmZvcm1hbmNlCiAgICBwcmVkaWN0ZGF0YSA8LSBwcmVkaWN0KFByZVByb2Nlc3NNb2RlbCwgdGVzdGRhdGEpCiAgICAKICAgIFBQcGVyZm9ybWFuY2UkbWV0aG9kIDwtIGltZXRob2QKICAgIFBQcGVyZm9ybWFuY2UkcHJlcHJvY2VzcyA8LSBpcHJlcHJvY2VzcwogICAgUFBwZXJmb3JtYW5jZSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgICBQUHBlcmZvcm1hbmNlIDwtIGFzLmRhdGEuZnJhbWUoUFBwZXJmb3JtYW5jZSkKICAgIFBQcGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kKFBQcGVyZm9ybWFuY2VfYWxsLFBQcGVyZm9ybWFuY2UpCiAgfQp9CiMgU29tZSBvZiB0aGUgbW9kZWxzIHdlcmUgbG9hZGVkIGludG8gQXp1cmVNTCBhbmQgcHJvY2Vzc2VkIHRoZXJlLgojIE91dHB1dCBmcm9tIEF6dXJlTUwKIyBbTW9kdWxlT3V0cHV0XSAgICAgICAgICBtZXRob2QgcHJlcHJvY2VzcyAgICAgUk1TRQojIFtNb2R1bGVPdXRwdXRdIFJNU0UgICAgICAga2tubiAgICAgQm94Q294IDEzMC43OTM5CiMgW01vZHVsZU91dHB1dF0gUk1TRTEgICAgICBra25uIFllb0pvaG5zb24gMTMwLjk2MDAKIyBbTW9kdWxlT3V0cHV0XSBSTVNFMiAgICAgIGtrbm4gICAgIGNlbnRlciAxMzAuNzMzMQojIFtNb2R1bGVPdXRwdXRdIFJNU0UzICAgICAga2tubiAgICAgIHNjYWxlIDEzMC4xODE4CiMgW01vZHVsZU91dHB1dF0gUk1TRTQgICAgICBra25uICAgICAgICBwY2EgMTMwLjIwNzEKIyBbTW9kdWxlT3V0cHV0XSBSTVNFNSBzdm1SYWRpYWwgICAgIEJveENveCAxNTQuMDg5OAojIFtNb2R1bGVPdXRwdXRdIFJNU0U2IHN2bVJhZGlhbCBZZW9Kb2huc29uIDE2OS45ODE2CiMgW01vZHVsZU91dHB1dF0gUk1TRTcgc3ZtUmFkaWFsICAgICBjZW50ZXIgMTU0LjE4OTEKIyBbTW9kdWxlT3V0cHV0XSBSTVNFOCBzdm1SYWRpYWwgICAgICBzY2FsZSAxNTQuMTAwMAojIFtNb2R1bGVPdXRwdXRdIFJNU0U5IHN2bVJhZGlhbCAgICAgICAgcGNhIDE2NC4wODgxCiMgc3ZtUmFkaWFsIGFuZCBra25uIGRvbid0IHBlcmZvcm0gYmV0dGVyIHdpdGggYW55IG9mIHRoZSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9ucyBpbiBwbGFjZQpgYGAKYGBge3J9ClBQcGVyZm9ybWFuY2VfYWxsCmBgYAoKIyMjIE1vZGVsIFByZWRpY3RvcnMKTm93IHdlIGNhbiByZXZpZXcgdGhlIHByZWRpY3RvcnMsIHRoZXJlIGFyZSBvbmx5IGEgZmV3IGZpZWxkcyBzbyBJIHdpbGwgbWFudWFsbHkgdGVzdCBwZXJmb3JtYW5jZQp3aGlsZSBleGNsdWRpbmcgZWFjaCBvZiB0aGVtIHRvIG1vbml0b3IgdGhlaXIgaW1wb3J0YW5jZS4KU29tZSBvZiBvdXIgZmllbGRzIGFyZSBpbnN0aW5jdGl2ZWx5IHJlcXVpcmVkIChZZWFyLCBVbml0KQpgYGB7cn0KUHJlZGljdG9ycyA8LSBjKCJRdW90YSIsIkRyYXduIikKUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKUHJlZGljdG9ycGVyZm9ybWFuY2UgPC0gTlVMTApmb3IgKGltZXRob2QgaW4gdG9wbWV0aG9kcykgewogIGZvciAoaXByZWRpY3RvciBpbiBQcmVkaWN0b3JzKSB7CiAgICByZWdpc3RlckRvU0VRKCkKICAgIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgICAKICAgIFByZWRpY3Rvck1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QodHJhaW5kYXRhLC1pcHJlZGljdG9yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGltZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogICAgCiAgICBwcmludChQcmVkaWN0b3JNb2RlbCkKICAgIAogICAgIyBjaGVjayBwZXJmb3JtYW5jZQogICAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChQcmVkaWN0b3JNb2RlbCwgdGVzdGRhdGEpCiAgICAKICAgIFByZWRpY3RvcnBlcmZvcm1hbmNlJG1ldGhvZCA8LSBpbWV0aG9kCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRtaXNzaW5nX3ByZWRpY3RvciA8LSBpcHJlZGljdG9yCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSA8LSBhcy5kYXRhLmZyYW1lKFByZWRpY3RvcnBlcmZvcm1hbmNlKQogICAgUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kKFByZWRpY3RvcnBlcmZvcm1hbmNlX2FsbCxQcmVkaWN0b3JwZXJmb3JtYW5jZSkKICB9Cn0KYGBgCgpgYGB7cn0KUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsCmBgYAoKCnN2TVJhZGlhbCB3aWxsIHBlcmZvcm0gYmV0dGVyIHdpdGggYWxsIG9mIHRoZSBwcmVkaWN0b3JzLCB3aGlsZSBra25uIHBlcmZvcm1zCmJldHRlciB3aXRoIG9ubHkgVW5pdCBhbmQgWWVhciBmaWVsZHMKClVzZSBhYm92ZSBpbmZvcm1hdGlvbiB0byB0ZXN0IG91dCB2YXJpb3VzIGNvbWJpbmF0aW9ucyBvZiBwcmVwcm9jZXNzaW5nIGFuZCBwcmVkaWN0b3Igc2V0cwoKIyMjIyMga2tubgpra25uIHdpdGhvdXQgUXVvdGEgYW5kIERyYXduCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAiYWRhcHRpdmVfY3YiLCAjcmVwZWF0ZWRjdiwgCiAgc2VhcmNoID0gJ3JhbmRvbScsCiAgbnVtYmVyID0gMTAsICM0CiAgcmVwZWF0cyA9IDEwLCAjMTAKICAjIGNsYXNzUHJvYnMgPSBUUlVFLAogICMgc2F2ZVByZWQgPSBUUlVFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICBtZXRob2QgPSAia2tubiIsCiAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA3NSwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKCmBgYApgYGB7cn0Ka2tubk1vZGVsCmBgYAoKQmVzdCBSTVNFCmBgYHtyfQojIG5vdCBzdXJlIHdoeSBjYXJldCBpcyBzZWxlY3RpbmcgcGFyYW1ldGVycyB3aXRoIGhpZ2hlciBSTVNFLCBsZXRzIHNlbGVjdCBtYW51YWxseQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubk1vZGVsJHJlc3VsdHMsIFJNU0UgPT0gbWluKFJNU0UpKQpSU01Fa2tubiRrZXJuZWwgPC0gYXMuY2hhcmFjdGVyKFJTTUVra25uJGtlcm5lbCkKUlNNRWtrbm4KYGBgCgojIyMgTW9kZWwgVHVuaW5nCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQgPC0gZGF0YS5mcmFtZShrbWF4ID0gYyhSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgsUlNNRWtrbm4ka21heCxSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZSA9IGMoUlNNRWtrbm4kZGlzdGFuY2UqLjcsUlNNRWtrbm4kZGlzdGFuY2UqLjksUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UqMS4xLFJTTUVra25uJGRpc3RhbmNlKjEuMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGMoUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCkpCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsICNyZXBlYXRlZGN2LCAKICBudW1iZXIgPSAxMCwgIzQKICByZXBlYXRzID0gMTAsICMxMAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uR3JpZE1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZCwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCmBgYHtyfQpra25uR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRQpgYGB7cn0KUlNNRWtrbm4gPC0gZmlsdGVyKGtrbm5HcmlkTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpClJTTUVra25uJGtlcm5lbCA8LSBhcy5jaGFyYWN0ZXIoUlNNRWtrbm4ka2VybmVsKQpgYGAKCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQyIDwtIGRhdGEuZnJhbWUoa21heCA9IGMoUlNNRWtrbm4ka21heCouNyxSU01Fa2tubiRrbWF4Ki45LFJTTUVra25uJGttYXgsUlNNRWtrbm4ka21heCoxLjEsUlNNRWtrbm4ka21heCoxLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZSA9IGMoUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSBjKFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwpKQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uR3JpZE1vZGVsMiA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gc2VsZWN0KENPRWxrSHVudGVycywtUXVvdGEsIC1EcmF3biksCiAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia2tubiIsCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZDIsCiAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpgYGB7cn0Ka2tubkdyaWRNb2RlbDIKYGBgCgoKT25lIG1vcmUgdGltZSBvbiBmaW5hbCBwYXJhbWV0ZXIgKGtlcm5lbCkKQmVzdCBSTVNFCmBgYHtyfQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubkdyaWRNb2RlbDIkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpWzEsXQprZXJuZWxzIDwtIGxldmVscyhra25uTW9kZWwkcmVzdWx0cyRrZXJuZWwpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9Cmtrbm5UdW5lR3JpZDMgPC0gZGF0YS5mcmFtZShrbWF4ID0gcmVwKDQ2NS4wLDgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2UgPSByZXAoMC4xMzk1NTg2LDgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0ga2VybmVscykKCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQoKa2tubkdyaWRNb2RlbDMgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZDMsCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKCmBgYAoKYGBge3J9Cmtrbm5HcmlkTW9kZWwzCmBgYAoKYGBge3J9ClJTTUVra25uIDwtIGZpbHRlcihra25uR3JpZE1vZGVsMyRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKYGBgCgpCZXN0IFJNU0UgZm9yIGtrbm4gdGh1cyBmYXIKYGBge3J9ClJTTUVra25uIDwtIGZpbHRlcihra25uTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKV29yayB0aHJ1IHNvbWUgcmVzYW1wbGluZyBtZXRob2RzIHdpdGggYmVzdCBra25uIHBhcmFtcwpgYGB7cn0Ka2tublR1bmVHcmlkNCA8LSBkYXRhLmZyYW1lKGttYXggPSBSU01Fa2tubiRrbWF4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2UgPSBSU01Fa2tubiRkaXN0YW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGFzLmNoYXJhY3RlcihSU01Fa2tubiRrZXJuZWwpKQoKdHJhaW5tZXRob2RzIDwtIGMoImJvb3QiLCAiYm9vdDYzMiIsICJvcHRpbWlzbV9ib290IiwgImJvb3RfYWxsIiwgImN2IiwgInJlcGVhdGVkY3YiLCAiTE9PQ1YiLCAiTEdPQ1YiLCAibm9uZSIpCnRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKZm9yIChpdHJhaW5tZXRob2QgaW4gdHJhaW5tZXRob2RzKSB7CiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSA8LSBOVUxMCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBpdHJhaW5tZXRob2QsCiAgICBudW1iZXIgPSAxMCwgIzQKICAgIHJlcGVhdHMgPSAxMCwgIzEwCiAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQogIAogIHJlZ2lzdGVyRG9TRVEoKQogIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgCiAga2tublRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBra25uVHVuZUdyaWQ0LAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAKICBwcmludChra25uVHJhaW5Nb2RlbCkKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlIDwtIGZpbHRlcihra25uVHJhaW5Nb2RlbCRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlJHRyYWlubWV0aG9kIDwtIGl0cmFpbm1ldGhvZAogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kLmZpbGwodHJhaW5tZXRob2RwZXJmb3JtYW5jZV9hbGwsdHJhaW5tZXRob2RwZXJmb3JtYW5jZSkKfQpgYGAKYGBge3J9CnRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsCmBgYAoKCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAib3B0aW1pc21fYm9vdCIsCiAgbnVtYmVyID0gMTAsICM0CiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpra25uRmluYWxUcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBDT0Vsa0h1bnRlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpzYXZlIG9mZiBmb3IgZnV0dXJlIGxvYWRpbmcKYGBge3J9CnNhdmUoa2tubkZpbmFsVHJhaW5Nb2RlbCwgZmlsZSA9ICJ+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9ra25uRmluYWxUcmFpbk1vZGVsLlJEYXRhIikKYGBgCiMjIE1vZGVsIFRlc3RpbmcKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Ka2tublRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHRyYWluZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoa2tublRyYWluTW9kZWwsIHRlc3RkYXRhKQoKcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycykKYGBgCgpDaGFydCBwZXJmb3JtYW5jZSBvZiBwcmVkaWN0ZWQKYGBge3J9CmNoYXJ0cGVyZm9ybWFuY2UgPC0gZGF0YS5mcmFtZShwcmVkaWN0ZWQgPSBwcmVkaWN0ZGF0YSwgb2JzZXJ2ZWQgPSB0ZXN0ZGF0YSRIdW50ZXJzKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KGNoYXJ0cGVyZm9ybWFuY2UsIGFlcyhwcmVkaWN0ZWQsb2JzZXJ2ZWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlPSJQZXJmb3JtYW5jZSBvZiBOdW1iZXIgb2YgSHVudGVycyBQcmVkaWN0aW9uIiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQpgYGAKCgoKIyMgU1ZNIApPdXRwdXQgZnJvbSBBenVyZU1MCltNb2R1bGVPdXRwdXRdIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIHdpdGggUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIEtlcm5lbCAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIDE1NDAgc2FtcGxlcwpbTW9kdWxlT3V0cHV0XSAgICA0IHByZWRpY3RvcnMKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIE5vIHByZS1wcm9jZXNzaW5nCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmc6IENyb3NzLVZhbGlkYXRlZCAoMTAgZm9sZCwgcmVwZWF0ZWQgMTAgdGltZXMpIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDEzODYsIDEzODYsIDEzODcsIDEzODcsIDEzODUsIDEzODUsIC4uLiAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmcgcmVzdWx0cyBhY3Jvc3MgdHVuaW5nIHBhcmFtZXRlcnM6CltNb2R1bGVPdXRwdXRdIApbTW9kdWxlT3V0cHV0XSAgIEMgICAgICAgIFJNU0UgIFJzcXVhcmVkICBSTVNFIFNEICBSc3F1YXJlZCBTRApbTW9kdWxlT3V0cHV0XSAgIDAuMjUgICAgIDI3NiAgIDAuOTQ2ICAgICAzMC42ICAgICAwLjAwODIyICAgIApbTW9kdWxlT3V0cHV0XSAgIDAuNSAgICAgIDIwMyAgIDAuOTYgICAgICAyMS40ICAgICAwLjAwNzU0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEgICAgICAgIDE4MCAgIDAuOTY1ICAgICAxNy43ICAgICAwLjAwNjcxICAgIApbTW9kdWxlT3V0cHV0XSAgIDIgICAgICAgIDE2OCAgIDAuOTY5ICAgICAxNS43ICAgICAwLjAwNjE0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDQgICAgICAgIDE1OCAgIDAuOTcyICAgICAxNC45ICAgICAwLjAwNTUgICAgIApbTW9kdWxlT3V0cHV0XSAgIDggICAgICAgIDE1MCAgIDAuOTc1ICAgICAxNC43ICAgICAwLjAwNTA2ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2ICAgICAgIDE0NiAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDgxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyICAgICAgIDE0NCAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDc3ICAgIApbTW9kdWxlT3V0cHV0XSAgIDY0ICAgICAgIDE0MyAgIDAuOTc3ICAgICAxNC42ICAgICAwLjAwNDc0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEyOCAgICAgIDE0MCAgIDAuOTc3ICAgICAxNC4zICAgICAwLjAwNDY5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI1NiAgICAgIDEzOSAgIDAuOTc4ICAgICAxNSAgICAgICAwLjAwNDg1ICAgIApbTW9kdWxlT3V0cHV0XSAgIDUxMiAgICAgIDEzNyAgIDAuOTc4ICAgICAxNC45ICAgICAwLjAwNDg0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwMjAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS4zICAgICAwLjAwNDk0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDIwNTAgICAgIDEzNSAgIDAuOTc5ICAgICAxNS42ICAgICAwLjAwNTEzICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxMDAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS41ICAgICAwLjAwNTEgICAgIApbTW9kdWxlT3V0cHV0XSAgIDgxOTAgICAgIDEzNyAgIDAuOTc4ICAgICAxNS43ICAgICAwLjAwNTE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2NDAwICAgIDEzOSAgIDAuOTc4ICAgICAxNi41ICAgICAwLjAwNTUxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyODAwICAgIDE0MSAgIDAuOTc3ICAgICAxNy42ICAgICAwLjAwNiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDY1NTAwICAgIDE0NSAgIDAuOTc2ICAgICAxOS40ICAgICAwLjAwNjU5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEzMTAwMCAgIDE1MSAgIDAuOTc0ICAgICAyMC44ICAgICAwLjAwNzE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI2MjAwMCAgIDE2MSAgIDAuOTcgICAgICAyNy4yICAgICAwLjAxMDQgICAgIApbTW9kdWxlT3V0cHV0XSAgIDUyNDAwMCAgIDQ3OCAgIDAuOCAgICAgICAzMjUgICAgICAwLjE3MiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwNTAwMDAgIDExODAgIDAuNSAgICAgICAxMDEwICAgICAwLjIwNCAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDIxMDAwMDAgIDMyNDAgIDAuMTQ4ICAgICAyMjYwICAgICAwLjExNyAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxOTAwMDAgIDYwMDAgIDAuMDYwNCAgICA2NDAwICAgICAwLjA1MjcgICAgIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gVHVuaW5nIHBhcmFtZXRlciAnc2lnbWEnIHdhcyBoZWxkIGNvbnN0YW50IGF0IGEgdmFsdWUgb2YgMC4wMDM3NjUzCltNb2R1bGVPdXRwdXRdIFJNU0Ugd2FzIHVzZWQgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nICB0aGUgc21hbGxlc3QgdmFsdWUuCltNb2R1bGVPdXRwdXRdIFRoZSBmaW5hbCB2YWx1ZXMgdXNlZCBmb3IgdGhlIG1vZGVsIHdlcmUgc2lnbWEgPSAwLjAwMzc3IGFuZCBDID0gMjA0OC4gCgoKIyBydW4gYWdhaW4gd2l0aCBhIHR1bmUgZ3JpZApgYGB7cn0Kc3ZtUmFkVHVuZUdyaWQgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBjKDAuMDAzNzY1MywwLjAwMzc2NTMsMC4wMDM3NjUzLDAuMDAzNzY1MywwLjAwMzc2NTMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBjKDIwNDgqLjcsMjA0OCouOSwyMDQ4LDIwNDgqMS4xLDIwNDgqMS4zKSkKCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgI3JlcGVhdGVkY3YsIAogIG51bWJlciA9IDEwLCAjNAogIHJlcGVhdHMgPSAxMCwgIzEwCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCnN2bVJhZEdyaWRNb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IHN2bVJhZFR1bmVHcmlkLAogICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCgpgYGB7cn0Kc3ZtUmFkR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRSwgbm90IHN1cmUgd2h5IGNhcmV0IGlzIHNlbGVjdGluZyBwYXJhbWV0ZXJzIHdpdGggaGlnaGVyIFJNU0UsIGxldHMgc2VsZWN0IG1hbnVhbGx5CmBgYHtyfQpSU01Fc3ZtUmFkIDwtIGZpbHRlcihzdm1SYWRHcmlkTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9CnN2bVJhZFR1bmVHcmlkMiA8LSBkYXRhLmZyYW1lKC5zaWdtYSA9IGMoUlNNRXN2bVJhZCRzaWdtYSouNyxSU01Fc3ZtUmFkJHNpZ21hKi45LFJTTUVzdm1SYWQkc2lnbWEsUlNNRXN2bVJhZCRzaWdtYSoxLjEsUlNNRXN2bVJhZCRzaWdtYSoxLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5DID0gYyhSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDLFJTTUVzdm1SYWQkQyxSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDKSkKCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQoKc3ZtUmFkR3JpZE1vZGVsMiA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDIsCiAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKYGBge3J9CnN2bVJhZEdyaWRNb2RlbDIKYGBgCgpgYGB7cn0KUlNNRXN2bVJhZCA8LSBmaWx0ZXIoc3ZtUmFkR3JpZE1vZGVsMiRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKYGBgCgpXb3JrIHRocnUgc29tZSByZXNhbXBsaW5nIG1ldGhvZHMgd2l0aCBiZXN0IGtrbm4gcGFyYW1zCmBgYHtyfQpzdm1SYWRUdW5lR3JpZDMgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBSU01Fc3ZtUmFkJHNpZ21hLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBSU01Fc3ZtUmFkJEMpCgp0cmFpbm1ldGhvZHMgPC0gYygiYm9vdCIsICJib290NjMyIiwgIm9wdGltaXNtX2Jvb3QiLCAiY3YiLCAicmVwZWF0ZWRjdiIsICJMT09DViIsICJMR09DViIsICJub25lIikKdHJhaW5tZXRob2RwZXJmb3JtYW5jZV9hbGwgPC0gTlVMTApmb3IgKGl0cmFpbm1ldGhvZCBpbiB0cmFpbm1ldGhvZHMpIHsKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlIDwtIE5VTEwKICBmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICAgIG1ldGhvZCA9IGl0cmFpbm1ldGhvZCwKICAgIG51bWJlciA9IDEwLCAjNAogICAgcmVwZWF0cyA9IDEwLCAjMTAKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBzdm1SYWRUcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBDT0Vsa0h1bnRlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAKICBwcmludChzdm1SYWRUcmFpbk1vZGVsKQogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2UgPC0gZmlsdGVyKHN2bVJhZFRyYWluTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSR0cmFpbm1ldGhvZCA8LSBpdHJhaW5tZXRob2QKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbCA8LSByYmluZC5maWxsKHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsLHRyYWlubWV0aG9kcGVyZm9ybWFuY2UpCn0KYGBgCmBgYHtyfQp0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbApgYGAKCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAib3B0aW1pc21fYm9vdCIsCiAgbnVtYmVyID0gMTAsICM0CiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpzdm1SYWRGaW5hbFRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IENPRWxrSHVudGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpzYXZlIG9mZiBmb3IgZnV0dXJlIGxvYWRpbmcKYGBge3J9CnNhdmUoc3ZtUmFkRmluYWxUcmFpbk1vZGVsLCBmaWxlID0gIn4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL3N2bVJhZEZpbmFsVHJhaW5Nb2RlbC5SRGF0YSIpCmBgYAoKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Kc3ZtUmFkVHJhaW5Nb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoc3ZtUmFkVHJhaW5Nb2RlbCwgdGVzdGRhdGEpCnBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpCmBgYAoKQ2hhcnQgcGVyZm9ybWFuY2Ugb2YgcHJlZGljdGVkCmBgYHtyIGZpZy53aWR0aD0xMH0KY2hhcnRwZXJmb3JtYW5jZSA8LSBkYXRhLmZyYW1lKHByZWRpY3RlZCA9IHByZWRpY3RkYXRhLCBvYnNlcnZlZCA9IHRlc3RkYXRhJEh1bnRlcnMpCmdncGxvdChjaGFydHBlcmZvcm1hbmNlLCBhZXMocHJlZGljdGVkLG9ic2VydmVkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZT0iUGVyZm9ybWFuY2Ugb2YgTnVtYmVyIG9mIEh1bnRlcnMgUHJlZGljdGlvbiIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgoKa2tubiBwZXJmb3JtZWQgYmV0dGVyIHRoYW4gc3ZtUmFkaWFsIFJNU0U9MTMwIHZzIDE1NApgYGB7cn0KRmluYWxIdW50ZXJzbW9kZWwgPC0ga2tubkZpbmFsVHJhaW5Nb2RlbAojIEZpbmFsSHVudGVyc21vZGVsIDwtIHN2bVJhZEZpbmFsVHJhaW5Nb2RlbApgYGAKYGBge3J9CnNhdmUoRmluYWxIdW50ZXJzbW9kZWwsIGZpbGUgPSAifi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvRmluYWxIdW50ZXJzbW9kZWwuUkRhdGEiKQpgYGAKClVzZSB0aGUgMjAxOCBEcmF3IGRhdGEgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgaW4gMjAxOApgYGB7cn0KCiMgZW5zdXJlIHdlIGluY2x1ZGUgYWxsIG9mIHRoZSB1bml0cwpDT0Vsa0h1bnRlcnMyMDE4IDwtIGFzLmRhdGEuZnJhbWUodW5pcXVlKENPRWxrSHVudGVycyRVbml0KSkKY29sbmFtZXMoQ09FbGtIdW50ZXJzMjAxOCkgPC0gIlVuaXQiCkNPRWxrSHVudGVyczIwMTgkWWVhciA8LSAyMDE4CgojIERyYXcgZGF0YSBmb3IgMjAxOApDT0Vsa0RyYXcyMDE4IDwtIGZpbHRlcihDT0Vsa0RyYXcsIFllYXIgPT0gMjAxOCkKCiMgQSBsZWZ0IGpvaW4gd2lsbCBhdXRvZmlsbCBtaXNzaW5nIGRyYXcgZGF0YSB3aXRoIE5BcywgYnV0IHdpbGwgcmV0YWluIHRoZSBmdWxsIGxpc3Qgb2YgVW5pdHMKQ09FbGtIdW50ZXJzMjAxOCA8LSBsZWZ0X2pvaW4oQ09FbGtIdW50ZXJzMjAxOCxDT0Vsa0RyYXcyMDE4KQojIFJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKQ09FbGtIdW50ZXJzMjAxOCREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMyMDE4JERyYXduKV0gPC0gMApDT0Vsa0h1bnRlcnMyMDE4JFF1b3RhW2lzLm5hKENPRWxrSHVudGVyczIwMTgkUXVvdGEpXSA8LSAwCgpDT0Vsa0h1bnRlcnMyMDE4IDwtIENPRWxrSHVudGVyczIwMThbLCBjb2xuYW1lcyhDT0Vsa0h1bnRlcnMyMDE4KSAlaW4lIGMoIlVuaXQiLEZpbmFsSHVudGVyc21vZGVsJGNvZWZuYW1lcyldCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnMgPC0gcm91bmQocHJlZGljdChGaW5hbEh1bnRlcnNtb2RlbCwgQ09FbGtIdW50ZXJzMjAxOCkpCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnNbQ09FbGtIdW50ZXJzMjAxOCRIdW50ZXJzPDBdIDwtIDAKYGBgCgpTYXZlIG9mZiBzbyB3ZSBkb24ndCBoYXZlIHRvIHJlY3JlYXRlIHRoZSBtb2RlbCBldmVyeXRpbWUgd2Ugd2FudCB0aGUgcmVzdWx0cwpgYGB7cn0Kc2F2ZShDT0Vsa0h1bnRlcnMyMDE4LGZpbGU9IkNPRWxrSHVudGVyczIwMTguUkRhdGEiKQpgYGAKCioqKgojIyBUb3RhbCBFbGsgSGFydmVzdAojIyMgU3RhdGV3aWRlCkdyb3VwIHNlYXNvbnMKYGBge3J9CkNPRWxrSHVudGVyc1N0YXRld2lkZSA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSHVudGVycyA9IHN1bShjKEh1bnRlcnMuQW50bGVyZWQsSHVudGVycy5BbnRsZXJsZXNzLEh1bnRlcnMuRWl0aGVyKSxuYS5ybSA9IFQpKQpDT0Vsa0h1bnRlcnMyMDE4YiA8LSBDT0Vsa0h1bnRlcnMyMDE4CiMgQ09FbGtIdW50ZXJzMjAxOGIkWWVhciA8LSBhcy5jaGFyYWN0ZXIoQ09FbGtIdW50ZXJzMjAxOGIkWWVhcikKCiMgSm9pbiAyMDE4IHRvIGhpc3RvcmljIGRhdGEKQ09FbGtIdW50ZXJzQWxsIDwtIHJiaW5kLmZpbGwoQ09FbGtIdW50ZXJzU3RhdGV3aWRlLENPRWxrSHVudGVyczIwMThiKQoKIyBHcm91cCBVbml0cwpDT0Vsa0h1bnRlcnNTdGF0ZXdpZGUgPC0gc3VtbWFyaXNlKGdyb3VwX2J5KENPRWxrSHVudGVyc0FsbCxZZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gc3VtKEh1bnRlcnMpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KENPRWxrSHVudGVyc1N0YXRld2lkZSwgYWVzKFllYXIsSHVudGVycykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsZmlsbD1nZ3RoZW1lc19kYXRhJGhjJHBhbGV0dGVzJGRlZmF1bHRbMl0pICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMTMwMDAwLDE1NTAwMCkpICsKICBsYWJzKHRpdGxlPSJTdGF0ZXdpZGUgRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyBjb21tZW50YXJ5CgoqKioKCiMjIyBIdW50ZXJzIGJ5IFVuaXQKSSdkIGxpa2UgdG8ga25vdyB3aGVyZSB0aGUgaHVudGVycyBhcmUgZGlzdHJpYnV0ZWQgYWNyb3NzIHRoZSBzdGF0ZS4KCk5leHQgeWVhcidzIGRhdGEKYGBge3J9ClllYXIyMDE4IDwtIGZpbHRlcihDT0Vsa0h1bnRlcnNBbGwsIFllYXIgPT0gIjIwMTgiKQpIdW50ZXJzdG9QbG90IDwtIGxlZnRfam9pbihVbml0Ym91bmRhcmllczIsWWVhcjIwMTgsIGJ5PWMoIlVuaXQiKSkKYGBgCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04LjQ2fQpnZ3Bsb3QoSHVudGVyc3RvUGxvdCwgYWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCkpICsgCiAgZ2VvbV9wb2x5Z29uKGFlcyhmaWxsID0gSHVudGVycyksY29sb3VyID0gImdyZXk1MCIsIHNpemUgPSAuMikgKyAjVW5pdCBib3VuZGFyaWVzCiAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgZ2VvbV90ZXh0KGRhdGE9ZGF0YV9jZW50cm9pZHMsYWVzKHg9bG9uZ2l0dWRlLHk9bGF0aXR1ZGUsbGFiZWwgPSBVbml0KSxzaXplPTMpICsgI1VuaXQgbGFiZWxzCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPcmFuZ2VzIixkaXJlY3Rpb24gPSAxLG5hLnZhbHVlID0gJ2xpZ2h0IGdyZXknKSArCiAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIDIwMTggQ29sb3JhZG8gRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyAtIGNvbW1lbnRhcnkKCioqKgoKIyMjIFllYXIgdG8gWWVhciBIdW50ZXIgVHJlbmRzCkNyZWF0ZSBhIHBuZyBvZiBlYWNoIHllYXIKYGBge3J9Cmljb3VudGVyIDwtIDAKZm9yIChpbWFwIGluIHVuaXF1ZShDT0Vsa0h1bnRlcnNBbGwkWWVhcikpewogICMgQ29sb3JhZG8gYXNwZWN0IHJhdGlvID0gMTA4N3cgeCA4MDBoIC0+IDEuMzU4NzUKICAjIFVzZSB0cmlhbCBhbmQgZXJyb3IgdG8gZGV0ZXJtaW5lIHdoaWNoIHdpZHRoIGFuZCBoZWlnaHQgdG8gZGVmaW5lIGZvciBwbmcgZmlsZXMgdGhhdCB3aWxsIHJldGFpbiB0aGUgY29ycmVjdCBhc3BlY3QgcmF0aW8KICBwbmcoZmlsZT1wYXN0ZSgiSHVudGVyc01hcCIsaW1hcCwiLnBuZyIpLCB3aWR0aD05NDgsIGhlaWdodD03MDApCiAgeWVhcnBsb3QgPC0gZmlsdGVyKENPRWxrSHVudGVyc0FsbCwgWWVhciA9PSBpbWFwKQogIEh1bnRlcnN0b1Bsb3QgPC0gbGVmdF9qb2luKFVuaXRib3VuZGFyaWVzMix5ZWFycGxvdCwgYnk9YygiVW5pdCIpKQogIHAxIDwtIGdncGxvdChIdW50ZXJzdG9QbG90LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKyAKICAgIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IEh1bnRlcnMpLGNvbG91ciA9ICJncmV5NTAiLCBzaXplID0gLjIpICsgI1VuaXQgYm91bmRhcmllcwogICAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgICBnZW9tX3RleHQoZGF0YT1kYXRhX2NlbnRyb2lkcyxhZXMoeD1sb25naXR1ZGUseT1sYXRpdHVkZSxsYWJlbCA9IFVuaXQpLHNpemU9NSkgKyAjVW5pdCBsYWJlbHMKICAgIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiT3JhbmdlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSAnbGlnaHQgZ3JleScsCiAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsbWF4KENPRWxrSHVudGVyc0FsbCRIdW50ZXJzKSkpICsgI2ZpeCBzbyBlYWNoIHllYXIgY2hhcnQgaGFzIHNhbWUgY29sb3IgYnJlYWtzCiAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdCA9IGljb3VudGVyL2xlbmd0aCh1bmlxdWUoQ09FbGtIdW50ZXJzQWxsJFllYXIpKSkpICsKICAgIGxhYnModGl0bGU9IkNvbG9yYWRvIEVsayBIdW50ZXJzIiwgc3VidGl0bGU9aW1hcCwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQogIHBsb3QocDEpCiAgZGV2Lm9mZigpCiAgaWNvdW50ZXIgPC0gaWNvdW50ZXIgKyAxCn0KYGBgCkNvbnZlcnQgdGhlIC5wbmcgZmlsZXMgdG8gb25lIC5naWYgZmlsZSB1c2luZyBJbWFnZU1hZ2ljay4gCmBgYHtyfQpzeXN0ZW0oImNvbnZlcnQgLWRlbGF5IDE1MCAqLnBuZyBIdW50ZXJzbWFwUHJlZC5naWYiKQpgYGAKCiFbXShIdW50ZXJzbWFwUHJlZC5naWYpCj4gVE9ETyAtIGNvbW1lbnRhcnkKClJlbW92ZSB0aGUgLnBuZyBmaWxlcwpgYGB7cn0KZmlsZS5yZW1vdmUobGlzdC5maWxlcyhwYXR0ZXJuPSIucG5nIikpCmBgYAoKKioqCiMjIyBOdW1iZXIgb2YgSHVudGVycyBSYW5rIG9mIHRoZSBVbml0cwpXb3VsZCBhbHNvIGJlIGJlbmVmaWNpYWwgdG8gcmFuayBlYWNoIHVuaXQgc28gSSBjYW4gcmVmZXJlbmNlIGxhdGVyLiBJbiB0aGlzIGNhc2UKYXZlcmFnZSB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgb2YgdGhlIGxhc3QgZmV3IHllYXJzCmBgYHtyfQpIdW50ZXJSYW5rMjAxOCA8LSBmaWx0ZXIoQ09FbGtIdW50ZXJzQWxsLCBhcy5udW1lcmljKFllYXIpID09IDIwMTgpCkh1bnRlclJhbmsyMDE4IDwtIHN1bW1hcmlzZShncm91cF9ieShIdW50ZXJSYW5rMjAxOCxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gbWVhbihIdW50ZXJzLG5hLnJtID0gVCkpCkh1bnRlclJhbmsyMDE4JEh1bnRlcnNSYW5rID0gcmFuaygtSHVudGVyUmFuazIwMTgkSHVudGVycykKCkh1bnRlclJhbmsyMDE4IDwtIGZpbHRlcihIdW50ZXJSYW5rMjAxOCwgSHVudGVyc1JhbmsgPD0gNTApICMgdG9wIDUwIHVuaXRzCiMgSW4gb3JkZXIgZm9yIHRoZSBjaGFydCB0byByZXRhaW4gdGhlIG9yZGVyIG9mIHRoZSByb3dzLCB0aGUgWCBheGlzIHZhcmlhYmxlIChpLmUuIHRoZSBjYXRlZ29yaWVzKSBoYXMgdG8gYmUgY29udmVydGVkIGludG8gYSBmYWN0b3IuCkh1bnRlclJhbmsyMDE4IDwtIEh1bnRlclJhbmsyMDE4W29yZGVyKC1IdW50ZXJSYW5rMjAxOCRIdW50ZXJzKSwgXSAgIyBzb3J0Ckh1bnRlclJhbmsyMDE4JFVuaXQgPC0gZmFjdG9yKEh1bnRlclJhbmsyMDE4JFVuaXQsIGxldmVscyA9IEh1bnRlclJhbmsyMDE4JFVuaXQpICAjIHRvIHJldGFpbiB0aGUgb3JkZXIgaW4gcGxvdC4KYGBgCgpMb2xsaXBvcCBDaGFydApgYGB7cn0KZ2dwbG90KEh1bnRlclJhbmsyMDE4LCBhZXMoeD1Vbml0LCB5PUh1bnRlcnMpKSArIAogIGdlb21fcG9pbnQoc2l6ZT0zKSArIAogIGdlb21fc2VnbWVudChhZXMoeD1Vbml0LCAKICAgICAgICAgICAgICAgICAgIHhlbmQ9VW5pdCwgCiAgICAgICAgICAgICAgICAgICB5PTAsIAogICAgICAgICAgICAgICAgICAgeWVuZD1IdW50ZXJzKSkgKyAKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgRWxrIEh1bnRlcnMgMjAxOFxuVG9wIDUwIFVuaXRzIiwgc3VidGl0bGU9Ikh1bnRlcnMgYnkgVW5pdCIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgo+IFRPRE8gLSBjb21tZW50YXJ5CgoqKioKIyMgQ29uY2x1c2lvbgo+IFRPRE8KCgo=